#pragma once

#include <cstddef>

struct Base64 {

    static inline size_t encode_size (size_t len)
    {
        return ((len + 2) / 3) * 4;
    }

    static void encode (void *dst_buf, const void *src_buf, size_t len)
    {
        static const char *table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        auto src = (const unsigned char*)src_buf;
        auto dst = (char*)dst_buf;
        for (; len >= 3; len -= 3, src += 3, dst += 4) {
            unsigned int b0 = src[0] >> 2;
            unsigned int b1 = ((src[0] << 4) & 0x30) | (src[1] >> 4);
            unsigned int b2 = ((src[1] << 2) & 0x3C) | (src[2] >> 6);
            unsigned int b3 = src[2] & 0x3F;
            dst[0] = table[b0];
            dst[1] = table[b1];
            dst[2] = table[b2];
            dst[3] = table[b3];
        }
        if (len == 2) {
            unsigned int b0 = src[0] >> 2;
            unsigned int b1 = ((src[0] << 4) & 0x30) | (src[1] >> 4);
            unsigned int b2 = (src[1] << 2) & 0x3C;
            dst[0] = table[b0];
            dst[1] = table[b1];
            dst[2] = table[b2];
            dst[3] = '=';
        }
        else if (len == 1) {
            unsigned int b0 = src[0] >> 2;
            unsigned int b1 = (src[0] << 4) & 0x30;
            dst[0] = table[b0];
            dst[1] = table[b1];
            dst[2] = '=';
            dst[3] = '=';
        }
    }


    static inline size_t decode_size (size_t len) {
        return (len >> 2) * 3;
    }

    static size_t decode (void *dst_buf, const void *src_buf, size_t len) {
        static const unsigned char table[256] =
            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
            "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3E\xFF\xFF\xFF\x3F"
            "\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\xFF\xFF\xFF\xFF\xFF\xFF"
            "\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E"
            "\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\xFF\xFF\xFF\xFF\xFF"
            "\xFF\x1A\x1B\x1C\x1D\x1E\x1F\x20\x21\x22\x23\x24\x25\x26\x27\x28"
            "\x29\x2A\x2B\x2C\x2D\x2E\x2F\x30\x31\x32\x33\xFF\xFF\xFF\xFF\xFF";
        auto src = (const unsigned char*)src_buf;
        auto dst = (unsigned char*)dst_buf;
        if (src[len - 1] == '=')
            len--;
        if (src[len - 1] == '=')
            len--;
        unsigned int b0, b1, b2, b3;
        for (; len >= 4; len -= 4, src += 4, dst += 3) {
            b0 = table[src[0]];
            b1 = table[src[1]];
            b2 = table[src[2]];
            b3 = table[src[3]];
            dst[0] = (b0 << 2) | ((b1 >> 4) & 0x3);
            dst[1] = (b1 << 4) | ((b2 >> 2) & 0xF);
            dst[2] = (b2 << 6) | b3;
        }
        if (len == 3) {
            b0 = table[src[0]];
            b1 = table[src[1]];
            b2 = table[src[2]];
            dst[0] = (b0 << 2) | ((b1 >> 4) & 0x3);
            dst[1] = (b1 << 4) | ((b2 >> 2) & 0xF);
            return 1;
        }
        if (len == 2) {
            b0 = table[src[0]];
            b1 = table[src[1]];
            dst[0] = (b0 << 2) | ((b1 >> 4) & 0x3);
            return 2;
        }
        return 0;
    }
};